/* COPYRIGHT_CHUNFENG */
#include "cf_cgi.h"
#include "cf_slist.h"
#include "cf_string.h"
#include "cf_common.h"
#include <cf_json.h>
#define ERROR_LOG_FILE "/tmp/log/cf_error.log"
#define MESSAGE_LOG_FILE "/tmp/log/cf_message.log"
#define DEBUG_LOG_FILE "/tmp/log/cf_debug.log"
#include "lcf_log.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef _WIN32_
#include <windows.h>
#else
#include <sys/mman.h>
#endif

CFCgiKeyFuncPair* cf_cgi_key_func_pair_new(const char* key, CFCgiShowFunc func)
{
	CFCgiKeyFuncPair *pair = (CFCgiKeyFuncPair*)malloc(sizeof(CFCgiKeyFuncPair));
	if(!pair){
		perror("Can not allocate memory for cgi func pair");
		return NULL;
	}
	pair->key = cf_string_new(key);
	pair->func = func;
    cf_list_init(&pair->list);
	return pair;
}

void cf_cgi_key_func_pair_free(CFCgiKeyFuncPair* pair)
{
	cf_string_free(pair->key);
	free(pair);
}

int cf_cgi_key_func_pair_append(CFCgiKeyFuncPair* head, const char* key, CFCgiShowFunc func)
{
	CFCgiKeyFuncPair* pair = cf_cgi_key_func_pair_new(key, func);
	if(pair){
		cf_list_insert_pre(&head->list, &pair->list);
		return 0;
	}
	else{
		return -1;
	}
}
CFCgiKeyFuncPair* cf_cgi_key_func_get_next(CFCgiKeyFuncPair* pair)
{
    CFListHead* list;
    list = cf_list_get_next(&pair->list);
    if(list)
        return container_of(list, CFCgiKeyFuncPair, list);
    else
        return NULL;
}

void cf_cgi_key_func_pair_free_all(CFCgiKeyFuncPair* pairHead)
{
    CFCgiKeyFuncPair* tmpNode = pairHead;
    do{
        CFCgiKeyFuncPair* next = cf_cgi_key_func_get_next(tmpNode);
        cf_cgi_key_func_pair_free(tmpNode);
        tmpNode = next;
    }while(tmpNode != pairHead);
}

static int __gen_key_value(char* at, char* sharp, CFCgiKeyFuncPair* keyFuncList, int fd)
{
	int res;
	CFString* keyStr;
	CFCgiKeyFuncPair* tmpNode;
	if(at == sharp){
		write(fd, "@#", 2);
		return 0;
	}
	keyStr = cf_string_new_start_end(at+1, sharp-1);
	tmpNode = keyFuncList;
	do{
		if(0 == strcmp(cf_string_get_str(tmpNode->key),
				cf_string_get_str(keyStr))){
			res = tmpNode->func(fd);
			break;
		}
		tmpNode = cf_cgi_key_func_get_next(tmpNode);
	}while(tmpNode != keyFuncList);
	if(!tmpNode)
		write(fd, cf_string_get_str(keyStr), cf_string_get_len(keyStr));
	cf_string_free(keyStr);
	return res;
}

/*
 * @key#
 *
 */
int cf_cgi_display_html(const char* htmFile, CFCgiKeyFuncPair* keyFuncList, int show_fd)
{
#ifdef _WIN32_
    HANDLE fHndl;
    HANDLE mHndl;
#else
	int fd;
#endif
	int res = 0;
	struct stat st;
	int fileLen;
	char *start, *end, *at, *sharp, *at2, *start2;
	if(-1 == stat(htmFile, &st)){
		perror("Can not stat html file");
		return -1;
	}
	fileLen = st.st_size;
#ifdef _WIN32_
    fHndl = CreateFile(htmFile, GENERIC_READ, FILE_SHARE_READ, 0,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if(INVALID_HANDLE_VALUE == fHndl){
        return NULL;
    }
    mHndl = CreateFileMapping(fHndl, 0, PAGE_READONLY, 0,
            0, NULL);
    if(INVALID_HANDLE_VALUE == mHndl){
        CloseHandle(fHndl);
        return NULL;
    }
    start = MapViewOfFile(mHndl, FILE_MAP_READ, 0, 0, 0);
    if(!start){
        CloseHandle(mHndl);
        CloseHandle(fHndl);
        return NULL;
    }
#else
	fd = open(htmFile, O_RDONLY);
	if(fd < 0){
		perror("Can not open html file");
		return -1;
	}

	if(NULL == (start = mmap(NULL, fileLen, PROT_READ, MAP_PRIVATE, fd, 0))){
		close(fd);
		perror("Can not map html file");
		return -1;
	}
#endif
	end = start + fileLen -1;
	start2 = start;
	while(1){
		at = memchr(start2, '@', end-start2+1);
		if(at){
			sharp = memchr(at, '#', end-at+1);
			if(sharp){
				while(1){
					at2 = memchr(at+1, '@', sharp-at);
					if(at2)
						at = at2;
					else
						break;
				}
                write(show_fd, start2, at-start2);
				__gen_key_value(at, sharp, keyFuncList, show_fd);
				start2 = sharp +1;
				continue;
			}
		}
		write(show_fd, start2, end-start2+1);
		break;
	}
#ifdef _WIN32_
    UnmapViewOfFile(start);
    CloseHandle(mHndl);
    CloseHandle(fHndl);
#else
	munmap(start, fileLen);
	close(fd);
#endif
	return res;
}

int CFCgiResponseHead(CFContentType contentType, int fd)
{
    CFFdPrintf(fd, "Content-Type:");
    switch(contentType){
        case CF_CONTENT_TYPE_TEXT_PLAIN:
            CFFdPrintf(fd, "text/plain");
            break;
        case CF_CONTENT_TYPE_TEXT_HTML:
            CFFdPrintf(fd, "text/html");
            break;
        default:
            CFFdPrintf(fd, "application/binary");
    }
    CFFdPrintf(fd, "\r\n\r\n");
    return 0;
}

int CFCgiResponseErrorBody(int errorCode, int fd)
{
    CFFdPrintf(fd, "{\"return\": %d}\n", errorCode);
    return 0;
}

int CFCgiResponseOkBody(int fd)
{
    CFFdPrintf(fd, "{\"return\": 200}\n");
    return 0;
}

int CFCgiProcessGetRequest(const char* queryCStr, RequestKeyFuncPair* KeyFuncPairs, int* stdio)
{
    CFString * todoStr = cf_string_new("");
    int i;

    if(cf_cgi_parse_query(queryCStr, "todo", todoStr)){
        LCF_ERR("Can not find todo key in  query string\n")
        goto OUT_FREE_TODO_STRING;
    }
    for(i=0; KeyFuncPairs[i].todo; i++){
        if(!strcmp(KeyFuncPairs[i].todo, cf_string_get_str(todoStr))){
            if(KeyFuncPairs[i].authSessionUseQueryString){
                if(KeyFuncPairs[i].authSessionUseQueryString(queryCStr, stdio)){
                    goto OUT_FREE_TODO_STRING;
                }
            }
            if(!KeyFuncPairs[i].processUseQueryString){
                CFCgiResponseHead(CF_CONTENT_TYPE_TEXT_PLAIN, stdio[1]);
                CFCgiResponseErrorBody(CF_CGI_RESPONSE_CODE_METHOD_NOT_ALLOWED, stdio[1]);
                goto OUT_FREE_TODO_STRING;
            }else if(KeyFuncPairs[i].processUseQueryString(queryCStr, stdio)){
                goto OUT_FREE_TODO_STRING;
            }
            break;
        }
    }
    if(!KeyFuncPairs[i].todo){
        LCF_ERR("todo=%s not support\n", cf_string_get_str(todoStr));
        CFCgiResponseHead(CF_CONTENT_TYPE_TEXT_PLAIN, stdio[1]);
        CFCgiResponseErrorBody(CF_CGI_RESPONSE_CODE_NOT_FOUND, stdio[1]);
        goto OUT_FREE_TODO_STRING;
    }
    cf_string_free(todoStr);
    return 0;

OUT_FREE_TODO_STRING:
    cf_string_free(todoStr);
    return -1;
}

int CFCgiProcessPostRequest(const char* postStr, RequestKeyFuncPair* KeyFuncPairs, int* stdio)
{
    CFJson *jReq;
    CFJson *todoJobj;
    const char* todoCStr;
    int i;

    jReq = CFJsonParse(postStr);
    if(!jReq){
        LCF_ERR("Parse jsong req failed\n");
        goto OUT;
    }
    todoJobj = CFJsonObjectGet(jReq, "todo");
    if(!todoJobj){
        LCF_ERR("Can not find todo key in json request\n");
        goto OUT_PARSE_JSON;
    }
    todoCStr = CFJsonStringGet(todoJobj);
    if(!todoCStr){
        LCF_ERR("todo key format is wrong\n");
        goto OUT_PARSE_JSON;
    }
    for(i=0; KeyFuncPairs[i].todo; i++){
        if(!strcmp(KeyFuncPairs[i].todo, todoCStr)){
            if(KeyFuncPairs[i].authSessionUseJason){
                if(KeyFuncPairs[i].authSessionUseJason(todoJobj, stdio)){
                    goto OUT_PARSE_JSON;
                }
            }
            if(!KeyFuncPairs[i].processUseJason){
                CFCgiResponseHead(CF_CONTENT_TYPE_TEXT_PLAIN, stdio[1]);
                CFCgiResponseErrorBody(CF_CGI_RESPONSE_CODE_METHOD_NOT_ALLOWED, stdio[1]);
                goto OUT_PARSE_JSON;
            }else if(KeyFuncPairs[i].processUseJason(jReq, stdio)){
                goto OUT_PARSE_JSON;
            }
            break;
        }
    }
    if(!KeyFuncPairs[i].todo){
        LCF_ERR("todo=%s not support\n", todoCStr);
        CFCgiResponseHead(CF_CONTENT_TYPE_TEXT_PLAIN, stdio[1]);
        CFCgiResponseErrorBody(CF_CGI_RESPONSE_CODE_NOT_FOUND, stdio[1]);
        goto OUT_PARSE_JSON;
    }
    CFJsonPut(jReq);
    return 0;

OUT_PARSE_JSON:
    CFJsonPut(jReq);
OUT:
    return -1;
}

int CFCgiProcessRequest(RequestKeyFuncPair* KeyFuncPairs, int* stdio)
{
    CFString* postStr = cf_string_new("");
    const char* queryCStr;
    const char* contentLenCStr;
    LCF_DBG("\n");
    queryCStr = getenv("QUERY_STRING");
    contentLenCStr = getenv("CONTENT_LENGTH");
        LCF_DBG("\n");
    CFCgiResponseHead(CF_CONTENT_TYPE_TEXT_PLAIN, stdio[1]);
        LCF_DBG("\n");
    if(!contentLenCStr && queryCStr && queryCStr[0]){
        LCF_DBG("\n");
        CFCgiProcessGetRequest(queryCStr, KeyFuncPairs, stdio);
    }else if(contentLenCStr){
        LCF_DBG("\n");
        cf_fd_to_string(postStr, stdio[0], atoi(contentLenCStr));
                LCF_DBG("postStr: %s, contentLenCStr:%s\n", cf_string_get_str(postStr), contentLenCStr);
        CFCgiProcessPostRequest(cf_string_get_str(postStr), KeyFuncPairs, stdio);
    }else{
        LCF_DBG("\n");
        CFCgiResponseErrorBody(400, stdio[1]);
    }
    cf_string_free(postStr);
    return 0;
}

